iT邦幫忙

2026 iThome 鐵人賽

DAY 22
0
AI Engineering

128GB 統一記憶體的三十天:DGX Spark 地端 LLM 與生成式 AI 部署實戰系列 第 22

Day 22|語意錨點對上 prefix cache,FreeToken VS vLLM,以及三個關於統一記憶體的教訓

  • 分享至 

  • xImage
  •  

https://ithelp.ithome.com.tw/upload/images/20260904/20141816yD92KBqtHu.png

今天原本要做的,和實際做的有差喔

昨天預告的是一場對決,vLLM 的 prefix cache 對上 FreeToken 的語意錨點快取,用三天累積的真實 agent 流量當測試資料。守方 vLLM 的成績單早就貼在擂台上,也就是 Day 21 文章中提到的 91.8% 的命中率。

測試結果是挑戰者輸在自己瞄準的那個位置上
FreeToken 的語意錨點宣稱能讓工具結果插在上下文中段時不必重算後綴,實測在同一份 45 輪真實 agent 序列上,它為那些輪次付的相對代價是 4.68 倍,守方 vLLM 是 3.03 倍。先講結論再講過程:

  • FreeToken 裝得上 GB10, 原本預期支援清單沒有 aarch64,實測這次則有。
  • 協同執行在兩個場地得到相反的決定,而且都是引擎自己量完硬體後決定的,桌機開,Spark 關。
  • 閘道的開銷只有 7 到 12 毫秒,所以使用閘道在現代的 AI 系統架構中是可採用的選項。
  • 在 WSL 上跑它要手動補六樣東西,官方文件一個字都沒提,最後仍然沒跑起來,改用 Windows 原生版才成功。
  • 它的思考關不掉,三種送法全部被靜默忽略,即使它自己宣告有 off 這一檔。
  • 測試本身碰到兩個問題,第一版算出的倍率誇大了將近一倍,修正後才是 4.68×。

https://ithelp.ithome.com.tw/upload/images/20260904/20141816XQnmhtBi9t.jpg

先把量尺校準,看閘道到底吃掉多少時間 ?

今天的測試因為要把所有計時統一在閘道側,所以這件事必須先弄清楚,否則後面每個數字都站在一個沒查證的假設上。

假設閘道慢
逐請求記錄器在 pre-call hook 裡把整份 messages 序列化寫進 JSONL,25k token 的 prompt 就是一次幾十 KB 的同步寫入。

測試方式比較簡單,用同一份 payload,交錯去連 AI 推論引擎 :8008 與走閘道 :8100,各十次,串流量 TTFT 取中位數。交錯而不是分批,是為了不讓快取升溫或某個忙碌的瞬間整段落在其中一邊。三種 prompt 大小分開量,預估時間成本應該隨 payload 放大。

閘道組態 小 234 字元 中 12,490 大 41,350
只掛 clamp 9.8 ms 6.9 ms 11.6 ms
clamp 加逐請求記錄器 6.9 ms 9.5 ms 11.6 ms

結果假設被推翻。 LiteLLM 的中位開銷是 7 到 12 毫秒,與 payload 大小無關,掛不掛記錄器完全沒有差別,即使它在那 60 個請求裡寫了 1.6 MB 也沒差。

兩個獨立量測比最大值之前,先確認它們算的是同一群請求。

順帶補上 Day 21 欠的另一個小數字,herdr 本體常駐 RSS 18.6 MB、VIRT 109 MB,三個 agent 全速跑時 CPU 落在 0 到 3.2%,因此指揮層工具的成本幾乎可以忽略,這使得我們日常使用指揮層工具、閘道等能夠更放心地去開來用。

擂台上守方的成績,與挑戰者的測試結果

對決的公平性則是從 Day 20 的閘道紀錄裡取出一段真實 agent 對話的 45 個請求,逐位元組原樣重送,不依賴模型回應,所以兩個引擎能吃到完全相同的序列,sha256 是 9e34e1f804d49faf…

這 45 輪裡有 23 輪是工具結果收尾的。這 23 輪是整場對決的重點,工具結果被接在上下文的中段,後面每一個 token 對位元組級前綴快取而言都成了新的,即使模型早就看過。語意錨點宣稱能解決的正是這個位置。

指標 vLLM 雙機 · prefix cache
牆鐘 / 錯誤 119.11 秒 / 0
全部輪 TTFT 中位 0.898 s
工具結果收尾那 23 輪 1.234 s
其餘 22 輪 0.407 s
倍率 3.03×
最差 TTFT 9.254 s
prefix cache 命中率 79.7%

位元組級前綴快取在工具插入的輪次上要付出三倍時間成本的代價。 圖上那幾塊灰底就是這 23 輪,線在裡面明顯抬高,是快取失效的結果。

挑戰者的線

https://ithelp.ithome.com.tw/upload/images/20260904/20141816N80qhV2KGX.png

守方 vLLM 挑戰者 FreeToken
硬體 Spark 雙機 GB10、統一記憶體 桌機 RTX 5060 Ti 16 GB + 128 GB
量化 FP8 ds_fp4(FTW)
工具收尾 23 輪 中位 1.234 s 33.84 s
其他 22 輪 中位 0.407 s 7.24 s
工具輪懲罰倍率 3.03× 4.68×
牆鐘 / 錯誤 119.11 s / 0 3358.8 s / 0

語意錨點在這份序列上沒有實現其效果。
它瞄準的正是「工具結果插在中段會讓位元組級前綴快取整段失效」這個弱點,實測它在那 23 輪付的相對代價比守方更高。

為什麼只能比倍率?
兩邊的硬體差了兩個顯著的等級(牆鐘 119.11 秒對 3358.8 秒),絕對延遲完全不可比。可比的是各自工具輪相對於自己非工具輪的倍率,因為那是引擎內部的量,硬體慢只會同時放大分子與分母。圖的縱軸就是照這個邏輯正規化的,1× 是該引擎的日常水準。

一個關不掉的不對稱因素。
FreeToken 的思考停不下來:reasoning_effort:"off"chat_template_kwargs:{"thinking":false}、以及完全不送參數,三種情況都吐出一模一樣的 63 個 reasoning chunk,即使它自己的 /v1/cache/status 宣告有 off 這一檔並列出對應的 enable_thinking: false。守方那邊是 DEFAULT_THINKING=off,這個差異沒辦法消除,只能說明它為什麼不推翻倍率的可讀性,因為思考的成本已經同時落在分子與分母上。

測試讓兩個引擎各自跑一次真實的 agent 任務

固定回放比的是同樣的輸入,但 agent 迴圈是回饋系統,AI 模型自己的答案會決定下一輪要做什麼。所以還要跑一趟自然的,比終點而不是比逐輪。任務同一句、harness 同一個(opencode)、工作區各給一份乾淨副本,內容是讀三個指定檔案、找出沒處理的錯誤路徑、寫成一份 issue 草稿。

守方 vLLM 挑戰者 FreeToken
牆鐘 62.8 秒 69.1 分鐘後我終止它
任務完成 是(79 行草稿) 否(沒有產出檔案)
prompt / output token 40,270 / 2,304 57,304 / 10,862
送到引擎的請求數 1 4
prefix cache 命中率 55.9% 未開啟 cache report

這一軌抓到了回放抓不到的東西。
回放只送不收,不依賴模型回應的品質,自然跑會,於是兩個在回放裡看不見的問題浮出來:

一、輸出混入日文。 中文任務、中文提示、中文工作區,transcript 裡卻散著 691 個日文字元、分佈在 303 處,例如「利用者の明示的意図(再生成)に反し、無声に続行」。這款是社群把 256 個專家剪到 128 的 REAP-145B,這種對模型的剪枝很可能傷到了多語言的一致性,而那是官方 checkpoint 上沒有的問題。

二、工具呼叫的 JSON 壞掉。 harness 收到 JSON Parse error: Unterminated string,agent 迴圈因此停在原地。輸出格式不合法,後面就走不下去了。

所以這一軌的結論並非 FreeToken 慢,而是這個組合在真實 agent 迴圈裡不可用。
慢是硬體的事,格式壞掉與語言漂移是模型的事,而兩者只有在真的讓它跑一次 agent 任務時才看得到。公平地看,這只是剪枝變體的問題,不是 FreeToken 引擎的問題,但那款剪枝變體正是它在 128 GB Windows 桌機上比較能裝得下的選擇之一。

量測本身踩過的兩個問題

這一節值得單獨寫,因為第一次測試的數字是錯的。

問題一:只認一個輸出通道。 第一次測試時腳本判定首 token 時只看 contenttool_calls。DeepSeek-V4 在這個負載下會把整個輸出預算花在 reasoning_content 上,finish_reasonlength,從不抵達 content。結果 45 輪裡有 14 輪被記成「沒有 TTFT」,而其中 9 輪正是工具收尾輪,也就是決定勝負的那一類。用那個子集算出來的倍率是 8.73×,比修正後的 4.68× 多了將近一倍。

問題二:重跑要留意快取。 修好腳本要重跑,但同一輪在熱快取下的 TTFT 是 7.19 秒,冷的時候是 218.12 秒,差 30 倍。所幸 FreeToken 有 /v1/cache/rebuild,用原有幾何重建一次就能清空池子,驗證方式是拿同一輪重測,得到 425.74 秒,比原本的冷啟還慢(因為原本那趟是循序回放,第 4 輪已經沾到前三輪的共用前綴)。確認清乾淨之後才重跑。

這兩個問題合起來,意味著,跨引擎比較之前,先確認測試方式認得對方所有的輸出通道,以及重跑測試不是在測試快取,那麼意義就會變小了。

部署 FreeToken 的眉角

它裝得上 aarch64 sm_121

原本預期本系列文章中,已經碰到有三次推論引擎的支援清單沒有 GB10 的前例,但這次不一樣。

PyPI 上的 freetoken 0.1.2 確實只有四個 manylinux_2_27_x86_64 的 wheel,沒有任何 ARM 發行版。但 0.1.1 帶了 sdist,pip 在 GB10 上原地編譯,產出了 freetoken-0.1.1-cp312-cp312-linux_aarch64.whl

Building wheel for freetoken (pyproject.toml): finished with status 'done'
Successfully installed freetoken-0.1.1 torch-2.11.0 flashinfer-python-0.6.18 triton-3.6.0

總共 45 分鐘,其中絕大部分不是編譯而是下載:aarch64 的 CUDA 堆疊 2.5 GB(cudnn 434 MB、cublas 296 MB、cusparselt 221 MB、nccl 182 MB)。

裝上不等於跑得動,所以下一步是簡單的驗證,跑 ft bench bw,它會用真 kernel 量硬體上限而不載入模型。五種資料格式 bf16、nvfp4、fp8、mxfp4、ds_fp4 全部跑完,sm_121 全過。

GGUF 讀取器只認得 gemma4

我還發現 FreeToken 的相依裡有 gguf,套件裡也有完整的 models/gguf/{reader,config,tokenizer}.py,一度以為找到了捷徑,不就可以把本機那款 86.7 GB 的 DeepSeek-V4-Flash IQ2XXS GGUF 拿來直接用,一個位元組都不用下載了嗎? 而且會變成兩個引擎吃同一款量化檔,順便解決原本規劃中「llama.cpp 只吃 GGUF、FreeToken 只吃 safetensors,沒有交集」的公平性問題。

實測一秒破功不能用:

ValueError: GGUF architecture 'deepseek4' is not supported (known: ['gemma4'])

讀取器在那裡,但只實作了一種架構,所以沒捷徑可用了。

在 WSL 上跑還得要手動補四樣東西

桌機這半的部署比 Spark 麻煩,而且麻煩的地方全部不在 FreeToken 自己身上,在 WSL 的 CUDA 環境。ft bench bw 連續失敗四次,每次補一樣:

  1. TVM_FFI_CUDA_ARCH_LIST=12.0。tvm-ffi 在 WSL 下抓不到計算能力,回 Could not detect CUDA compute_cap automatically。卡是 RTX 5060 Ti,sm_120。
  2. CUDA_HOME。WSL 裡沒有系統層的 CUDA(/usr/local/cuda 不存在),只有 pip 裝進 venv 的那套,要手動指過去。
  3. ninjanvcc。前者是 JIT 編譯要用的,後者 venv 裡原本沒有,nvidia-cuda-nvcc-cu12 才把它補進來。
  4. 兩個符號連結。wheel 把函式庫放在 lib/ 而編譯腳本找 lib64/,而且只給 libcudart.so.13 沒有連結器要的 libcudart.so 別名。

這四樣寫成一個 bench.sh 存進 scripts/desktop-bench.sh。任何想在 WSL 上跑 FreeToken 的人都會碰到這問題,而官方文件一個字都沒提。相對地,NVIDIA DGX Spark 上有系統 CUDA,ft bench bw 直接就跑起來了。

還是會碰到記憶體的問題

論文把記憶體需求寫得很死:

"The CPU-resident expert pool holds the complete routed-expert weights and remains the source of truth"

它不從磁碟串流,推論期間整個專家池必須常駐主記憶體。 FreeToken 那篇論文自己跑 284B 的 DeepSeek-V4-Flash 用的是 RTX 5090 加 180 GB DDR5,並明講那個池子約 140 GB。

於是兩個場地都不夠,桌機 128GB、Spark 128GB 都不能直接跑 284B 的 DeepSeek-V4-Flash。Spark 上的 vLLM 之所以跑得動同一款模型,是因為 TP=2 拆在兩台機器上,每台只扛一半,而 FreeToken 的 TP 是單機多卡,GB10 一台只有一款 GPU。

能裝得下的最小候選是社群剪枝的 REAP-145B,83.3 GB,路由專家從 256 剪到 128。這款就是對決第二條線要用的權重。

同一條規則,兩台機器相反的決定

即使兩個場地都沒能載入模型,ft bench bw 已經回答了我們本來想問的問題,且比我原本規劃的實驗更簡單扼要,它不需要跑模型才能知道,因為它會先自己測試硬體能不能用。

FreeToken 啟動時會自己測試兩種路線,看專家 MOE 是留在主記憶體由 CPU 算,還是搬進 GPU 由 GPU 算。只有當 CPU 那條快到 PCIe 的兩倍以上,它才會自動選 CPU 協同執行。

https://ithelp.ithome.com.tw/upload/images/20260904/20141816oNYhD9KA9k.png

兩個場地各自測試完之後,答案是相反的:

資料格式 桌機比值 桌機選擇 Spark 比值 Spark 選擇
bf16 2.64× hybrid 0.66× offload
nvfp4 2.15× hybrid 0.39× offload
fp8 n/a n/a n/a offload
mxfp4 1.08× offload 0.26× offload
ds_fp4 1.72× offload 0.33× offload

桌機 這邊,CPU STREAM 讀 35.7 GB/s、PCIe 13.4 GB/s,CPU 記憶體頻寬是 PCIe 的 2.7 倍。
bf16 與 nvfp4 的比值越過 2.0 的門檻,FreeToken 這時候開啟 hybrid。

Spark 呢,CPU STREAM 讀 104.5 GB/s、所謂的 PCIe 58.8 GB/s。五種格式全部落在 0.26 到 0.66,一個都沒選 CPU 協同執行。

原因就是統一記憶體,在 GB10 上,「把專家 MOE 搬去 GPU」和「留在原地用 CPU 算」讀的是同一塊 LPDDR5X。搬運那條路不可能便宜到計算的兩倍,因為它們是同一條頻寬。那個所謂的 PCIe 數字,量的其實是同一塊 DRAM 的複製速度。

同一個引擎、同一條規則、兩種硬體,得到相反的決定。 這是 Day 14 的 n-gram 卸載之後,第二個「為分離式架構設計的聰明,在具備統一記憶體的機器上變成空轉」的案例,而且這次有了對照組,這樣的設計,在它該有價值的機器上,如 Windows 桌機,真的被啟用了。

跟 Day 14 還有一個重要差別,這種事情是 FreeToken 自己測得出來,然後主動放棄執行的。 它不是白跑,是先花幾秒鐘確認這台機器不需要它,然後走另一條路,這可說是好的工程設計。。

還有一件事值得留意,即使在它的主場,協同執行也只是輔助。 桌機上選了 hybrid 的 bf16,實際上只讓 CPU 接手約 27% 的 miss,其餘七成還是搬去 GPU 算。所以「CPU 與 GPU 協同執行」不是把工作對半分,是拿 CPU 去填 PCIe 來不及搬的那個缺口。

兩個案例湊在一起,就能給讀者一條選購時的判斷,看到任何一個宣稱能加速的最佳化,先問它在解哪一種記憶體架構的問題。
我這幾篇常提到的卸載、預取、協同執行、分層放置等概念,這一整群的技術前提,都是有一座窄橋,因此當橋不存在的時候,它們最好的結果是識相地不啟用。

測試時要注意閒置後的第一趟結果需要被忽略

這個結論差點被我寫錯。前兩趟 ft bench bw(都是機器閒置一段時間之後跑的)把 CPU STREAM 量成約 55 GB/s,而不是穩態的 104 GB/s,比值因此被高估到足以翻轉後端選擇,其中一趟 bf16 衝到 3.54× 越過門檻,FreeToken 真的選了 hybrid。

連跑六趟才看清楚:r4 到 r6 三趟高度一致(bf16 0.66、0.67、0.66),屬於離群數值的永遠是第一趟。那是 CPU 還沒升頻的假象,不是硬體的性質。桌機則沒有這個現象,六趟的 CPU STREAM 落在 35.1 到 36.2,PCIe 固定 13.4,穩得可以直接取全部六趟的中位數。所以這是 GB10 的特性而不是這個工具的通病。任何會自己量硬體再做決策的引擎,都要問它在什麼狀態下量的。 它在你的機器剛開機時做的決定,可能跟穩態時不一樣。

對 Day 11 那個判斷的裁決

Day 11 說「AI 代理人平台選 vLLM」。今天挑戰者正面上場了,結果是維持這個說法不變,而且理由比原本更清楚。

守方的弱點是真的,但挑戰者沒有解決它。 45 輪回放裡,工具結果收尾的 23 輪對 vLLM 要付 3.03 倍的 TTFT 代價,位元組級前綴快取在 agent 迴圈這種不斷在中段插入內容的用法下確實有結構性損失。語意錨點瞄準的位置完全正確。但在同一份序列上,FreeToken 自己付的是 4.68 倍,比守方更高。弱點被指出來了,但沒有被補上。

挑戰者的另一半武器,在 Spark 上先天失效。 協同執行是為了「VRAM 裝不下、主記憶體裝得下、中間有一座窄橋」設計的。Spark 沒有那座橋,引擎自己量完就放棄了。所以 FreeToken 對 Spark 使用者的價值本來就全部押在語意錨點上,而那一半今天也沒有實現。

它真正的價值在別的地方,而且今天也測試到了。 也就是說,桌機那台 16 GB 的 NVIDIA 顯示卡,靠著 68.5 GB 的專家池加上 11 層 CPU 解碼,真的把一款大型參數 145B 的 MoE 地端模型跑起來了,45 輪一次沒錯,這可是 vLLM 在同一台機器上做不到的事哪。

它的協同執行長這樣,這是 Spark 上測試不不到的對照組:

--moe-cpu-layers auto: banks 68.53 GiB > pin budget 51.13 GiB,
locking 11 head+tail MoE layers for CPU decode ([0,1,2,3,4,5,38,39,40,41,42])
MoE bank split residency: 51.00 GiB pinned (32 GPU layers)
                        + 17.53 GiB OS-locked (11 CPU layers)

68.5 GB 的專家池塞不進 51.1 GB 的 pin 預算,於是頭尾 11 層鎖在 CPU 解碼、中間 32 層走 GPU。實測 decode 6.15 tok/s(256 token、TTFT 7.51 秒、全程 48.8 秒)。

原本規劃要拿 llama.cpp 在同一台機器上比,但手上兩邊的權重是兩款不同的模型,FreeToken 跑的是剪枝到 128 專家的 REAP-145B,本機的 GGUF 是完整 256 專家的 DS4-Flash-0731。連模型都不同的 decode 對比會誤導,所以不做。如果用比例尺來類比的話,Day 19 在 Spark 上量過 vLLM 對 llama.cpp 的 decode 是 7.8 對 7.09 t/s,但那是另一款模型、另一種記憶體架構,只能當參考,不是這台機器可對照的結果喔。

所以 FreeTOken 正確的定位,並不是「它來搶 vLLM 的生意」,而是「它讓沒有 Spark 的人也能跑旗艦 MoE」,代價是慢兩個等級。

至於那個「一張 16GB 的卡跑一款旗艦 MoE」的故事,今天測試到兩個沒人在官方宣傳裡講的前提,你要這樣在桌機上跑大模型,那張顯示卡卡旁邊至少要插 128 GB 記憶體(論文自己的環境甚至是 180 GB),而且慢到什麼程度要自己有心理準備,真的很慢。這對「地端 AI 入場券價格」的計算影響很大,這系列文章後面的成本篇會把它算進去分享給大家。

明天預告

明天換節奏進生成媒體週,包括ComfyUI 部署與 NFS 模型庫的整合,把 Day 5 那套集中管理的哲學延伸到 checkpoint、LoRA 與 VAE 的世界。今天用到的桌機 + NVIDIA 5060 Ti 16GB,明天會以生圖節點的身分留在場上。

我們 Day 23 見囉。
Day 1|為什麼 2026 年是地端 AI 部署元年:系列規劃與硬體總覽
Day 2|DGX Spark GB10 深度解析:128GB 統一記憶體到底解決了什麼問題
Day 3|網路與儲存規劃:10GbE 骨幹、雙 Spark 直連與 NFS 集中模型庫
Day 4|開箱之後:DGX OS 初始環境建置與 CUDA、Docker 生態確認
Day 5|NFS 模型庫實戰:下載工具、權限設計與版本管理
Day 6|llama.cpp、vLLM、TensorRT-LLM 、Ollama、DS4 與 Unsloth 的定位與取捨
Day 7|第一個模型上線:gpt-oss-120b 從模型庫到 API 的完整流程
Day 8|llama.cpp 實測:先定量尺與 SOP
Day 9|vLLM 實測:官方容器、同時處理吞吐曲線,與剛出爐的新模型 Ornith 1.5
Day 10|TensorRT-LLM 實測:我花了一個下午,跟它的預設值搏感情
Day 11|地端 AI 三大推論引擎綜合評測,同場加映神秘嘉賓
Day 12|量化格式解析:「4-bit」兩個字,古今多少事 ? 都付笑談中
Day 13|Perplexity 把整套 Agent 搬上 DGX Spark:地端元年的官方背書
Day 14|Qwen3.8-27B 與 Flash-Next 加碼實測:地端模型世代對決與落地評估
Day 15|Ornith 1.5 實測:為 AI agent 而生的 35B-A3B
Day 16|模型選型方法論:五個問題幫你跳脫排行榜迷失,找到適合自己任務用的 AI 模型
Day 17|ds4 深度解析:Redis 之父只為一種模型造了一個引擎
Day 18|OpenAI 相容 API 層:地端 AI 多模型常駐好助手
Day 19|地端 agent 框架總覽:它們到底對你的端點做了什麼呢?
Day 20|DeepSeek Harness 實戰:21 萬顆星的外掛式 harness 接上地端
Day 21|herdr 是地端 AI 同時跑多個 AI agent 的最佳解
Day 22|語意錨點對上 prefix cache,FreeToken VS vLLM,以及三個關於統一記憶體的教訓
Day 23|ComfyUI 部署與 NFS 模型庫整合:把集中管理的哲學帶進生圖世界
Day 24|MiniMax-H3 地端影片生成:h3-ui 與四步蒸餾版的三段對決
Day 25|Unsloth 微調實戰:教一款模型用繁體中文思考,並遵守台灣的編輯規範
Day 26|微調的最後一哩路:合併、轉檔、量化、上線


上一篇
Day 21|herdr 是地端 AI 同時跑多個 AI agent 的最佳解
下一篇
Day 23|ComfyUI 部署與 NFS 模型庫整合:把集中管理的哲學帶進生圖世界
系列文
128GB 統一記憶體的三十天:DGX Spark 地端 LLM 與生成式 AI 部署實戰28
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言